package com.wsoft.bpm.service.impl;

import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.json.JSONObject;
import cn.hutool.json.JSONUtil;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.ObjectNode;
import com.github.pagehelper.PageHelper;
import com.wsoft.bpm.bean.ActModelMetaInfo;
import com.wsoft.bpm.component.CustomBpmnJsonConverter;
import com.wsoft.bpm.dto.ActModelDTO;
import com.wsoft.bpm.dto.ActModelDataDTO;
import com.wsoft.bpm.entity.ActReModelEntity;
import com.wsoft.bpm.entity.ActWorkflowConfigEntity;
import com.wsoft.bpm.mapper.ActReModelMapper;
import com.wsoft.bpm.query.model.ActModelQuery;
import com.wsoft.bpm.query.node.TaskQuery;
import com.wsoft.bpm.service.IActReModelService;
import com.wsoft.bpm.service.IActWorkflowConfigService;
import com.wsoft.constant.CommonConstant;
import com.wsoft.constant.RedisKeyConstant;
import com.wsoft.core.exception.Asserts;
import com.wsoft.core.service.RedisService;
import com.wsoft.entity.BaseEntity;
import lombok.SneakyThrows;
import lombok.extern.log4j.Log4j2;
import org.activiti.bpmn.converter.BpmnXMLConverter;
import org.activiti.bpmn.model.Process;
import org.activiti.bpmn.model.*;
import org.activiti.editor.constants.ModelDataJsonConstants;
import org.activiti.editor.language.json.converter.BpmnJsonConverter;
import org.activiti.engine.RepositoryService;
import org.activiti.engine.repository.Deployment;
import org.activiti.engine.repository.DeploymentBuilder;
import org.activiti.engine.repository.Model;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;
import javax.xml.stream.XMLInputFactory;
import javax.xml.stream.XMLStreamReader;
import java.io.BufferedReader;
import java.io.ByteArrayInputStream;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.nio.charset.StandardCharsets;
import java.util.Collection;
import java.util.List;
import java.util.stream.Collectors;

/**
 * @Author zhonglj
 * @Date 2024-08-28 13:02
 */
@Service
@Log4j2
public class ActReModelServiceImpl extends ServiceImpl<ActReModelMapper, ActReModelEntity> implements IActReModelService {
    @Resource
    private RepositoryService repositoryService;
    @Resource
    private ObjectMapper objectMapper;
    @Resource
    private RedisService redisService;
    @Resource
    private IActWorkflowConfigService actWorkflowConfigService;

    @Override
    public String deploy(String modelId) {
        try {
            Model model = repositoryService.getModel(modelId);
            ObjectNode modelNode = (ObjectNode) new ObjectMapper().readTree(repositoryService.getModelEditorSource(model.getId()));
            byte[] bpmnBytes = null;
            BpmnJsonConverter bpmnJsonConverter = new BpmnJsonConverter();
            BpmnModel bpmnModel = bpmnJsonConverter.convertToBpmnModel(modelNode);
            bpmnBytes = new BpmnXMLConverter().convertToXML(bpmnModel);
            String processName = model.getName() + ".bpmn20.xml";
            //部署名称
            DeploymentBuilder deploymentBuilder = repositoryService.createDeployment().name(model.getName());
            //完成部署
            Deployment deployment = deploymentBuilder.addString(processName, new String(bpmnBytes, StandardCharsets.UTF_8)).deploy();

            return deployment.getId();
        } catch (Exception e) {
            log.error(e.getMessage(), e);
            Asserts.fail("[bpm.model.error.deploy.fail]");
        }
        return null;
    }

    @Override
    public List<ActModelDTO> pageList(ActModelQuery reqVO) {
        PageHelper.startPage(reqVO.getPageNum(), reqVO.getPageSize());
        List<ActReModelEntity> list = list();
        if (CollUtil.isNotEmpty(list)) {
            List<ActModelDTO> dtos = list.stream().map(e -> {
                ActModelDTO dto = new ActModelDTO();
                dto.setId(e.getId());
                dto.setName(e.getName());
                dto.setKey(e.getKey());
                dto.setCategory(e.getCategory());
                String description = JSONUtil.parseObj(e.getMetaInfo()).getStr("description");
                dto.setDesc(description);
                return dto;
            }).collect(Collectors.toList());

            return dtos;
        }
        return null;
    }

    @Override
    public void saveModelData(ActModelDataDTO modelDTO) {
        try {
            String data = modelDTO.getData();
            String modelId = modelDTO.getModelId();
            Model modelData = repositoryService.getModel(modelId);

            InputStream stream = new ByteArrayInputStream(data.getBytes());
            XMLInputFactory xif = XMLInputFactory.newInstance();
            InputStreamReader in = new InputStreamReader(stream, StandardCharsets.UTF_8);
            XMLStreamReader xtr = xif.createXMLStreamReader(in);
            BpmnModel bpmnModel = new BpmnXMLConverter().convertToBpmnModel(xtr);
            //必要的校验
            checkBpmnModel(bpmnModel, modelId);
            //解析
            BpmnJsonConverter converter = new BpmnJsonConverter();
            ObjectNode modelNode = converter.convertToJson(bpmnModel);
            //保存模型
            repositoryService.saveModel(modelData);
            //保存资源
            repositoryService.addModelEditorSource(modelData.getId(), modelNode.toString().getBytes(StandardCharsets.UTF_8));

            //保存页面配置的json数据
            String key = StrUtil.format(RedisKeyConstant.MODEL_DATA_CACHE_KEY_FIND_BY_ID, modelId);
            List<Object> objects = redisService.getByKeyPattern(key);

            if (CollUtil.isNotEmpty(objects)) {
                // 删除当前模型未删除、未部署的json配置，始终保持编辑模型数据只有一份
                actWorkflowConfigService.remove(Wrappers.lambdaQuery(ActWorkflowConfigEntity.class)
                        .eq(ActWorkflowConfigEntity::getModelId, modelId)
                        .eq(BaseEntity::getDeleted, CommonConstant.YESNO_NO)
                        .isNull(ActWorkflowConfigEntity::getProcDefId)
                );

                List<ActWorkflowConfigEntity> entities = objects.stream().map(e -> {
                    ActWorkflowConfigEntity entity = new ActWorkflowConfigEntity();
                    entity.setModelId(modelId);
                    JSONObject jsonObject = JSONUtil.parseObj(e);
                    String id = jsonObject.getStr("id");
                    if (StrUtil.isNotBlank(id)) {
                        entity.setActId(id);
                    }
                    entity.setJson(JSONUtil.toJsonStr(jsonObject));

                    return entity;
                }).collect(Collectors.toList());

                actWorkflowConfigService.saveBatch(entities);

                // 成功保存数据库之后清空缓存
                redisService.deleteByKeyPattern(key);
            }
        } catch (Exception e) {
            log.error(e.getMessage(), e);
            Asserts.fail("[bpm.model.error.save.fail]");
        }
    }

    private void checkBpmnModel(BpmnModel bpmnModel, String modelId) {
        if (bpmnModel == null) {
            return;
        }

        // 校验模版中动态上级
        checkDynamicLeader(bpmnModel, modelId);
    }

    private void checkDynamicLeader(BpmnModel bpmnModel, String modelId) {
        // 遍历获取task
        Process process = bpmnModel.getProcesses().get(0);
        Collection<FlowElement> flowElements = process.getFlowElements();
        // 遍历所有流程元素，找出任务节点
        for (FlowElement flowElement : flowElements) {
            if (flowElement instanceof UserTask) {
                String key = StrUtil.format(RedisKeyConstant.MODEL_DATA_CACHE_KEY_TEMPLATE, modelId, flowElement.getId());
                Object value = redisService.get(key);

                if (value == null) {
                    continue;
                }

                if (value != null) {
                    TaskQuery taskQuery = JSONUtil.toBean(value.toString(), TaskQuery.class);
                    if (StrUtil.isBlank(taskQuery.getDynamicApprovalType())) {
                        // 非动态上级，跳过
                        continue;
                    }
                }

                // 填充上一个用户节点
                List<FlowElement> userTasks = CollUtil.newArrayList();
                fillFirstUserTaskBefore(process, flowElement, userTasks, true);

                // 进行参数校验
                if (userTasks.size() == 0) {
                    Asserts.fail("[bpm.process.error.checkDynamicLeader.size.zero]");
                } else if (userTasks.size() >= 2) {
                    Asserts.fail("[bpm.process.error.checkDynamicLeader.size.two]");
                } else {
                    // 判断是否有分配人
                    UserTask userTask = (UserTask) userTasks.get(0);
                    if (StrUtil.isBlank(userTask.getAssignee())) {
                        Asserts.fail("[bpm.process.error.checkDynamicLeader.assign.no]");
                    }
                }


            }
        }
    }

    /**
     * 获取当前节点每个方向上的第一个前置用户任务到列表
     *
     * @param process
     * @param currentElement
     * @param userTasks
     * @return void
     * @author zhonglj
     * @date 2024-10-14 17:06
     **/
    private void fillFirstUserTaskBefore(Process process, FlowElement currentElement, List<FlowElement> userTasks, boolean first) {
        // 如果当前节点是UserTask，直接返回
        if (!first && currentElement instanceof UserTask) {
            userTasks.add(currentElement);
            return;
        }
        FlowNode currentNode = (FlowNode) currentElement;
        // 获取当前节点的所有入线
        List<SequenceFlow> incomingFlows = currentNode.getIncomingFlows();
        // 遍历所有入线，递归查找
        for (SequenceFlow incomingFlow : incomingFlows) {
            FlowElement sourceElement = process.getFlowElement(incomingFlow.getSourceRef());
            fillFirstUserTaskBefore(process, sourceElement, userTasks, false);
        }
    }

    @Override
    @SneakyThrows
    public String createModel(ActModelDTO modelDTO) {
        String modelName = modelDTO.getName();
        String key = modelDTO.getKey();
        String category = modelDTO.getCategory();
        String desc = modelDTO.getDesc();

        //初始化一个空模型
        Model model = repositoryService.newModel();

        //ModelEditorSource
        ObjectNode editorNode = objectMapper.createObjectNode();
        editorNode.put("id", "canvas");
        editorNode.put("resourceId", "canvas");
        ObjectNode stencilSetNode = objectMapper.createObjectNode();
        stencilSetNode.put("namespace", "http://b3mn.org/stencilset/bpmn2.0#");
        stencilSetNode.put("author", "process_author");
        editorNode.set("stencilset", stencilSetNode);
        ObjectNode propertiesNode = objectMapper.createObjectNode();
        //流程唯一标识
        propertiesNode.put("process_id", key);
        //流程作者
        propertiesNode.put("process_author", "process_author");
        //流程名称
        propertiesNode.put("name", modelName);
        editorNode.set("properties", propertiesNode);


        ObjectNode modelNode = objectMapper.createObjectNode();
        modelNode.put(ModelDataJsonConstants.MODEL_DESCRIPTION, desc);
        //模型分类
        model.setCategory(category);
        model.setKey(key);
        model.setMetaInfo(modelNode.toString());
        //模型名称
        model.setName(modelName);

        repositoryService.saveModel(model);
        String id = model.getId();
        repositoryService.addModelEditorSource(id, editorNode.toString().getBytes(StandardCharsets.UTF_8));
        return id;
    }

    @Override
    public String xmlView(String modelId) {
        try {
            Model modelData = repositoryService.getModel(modelId);
            BpmnJsonConverter jsonConverter = new BpmnJsonConverter();
            ObjectNode modelNode = (ObjectNode) new ObjectMapper().readTree(repositoryService.getModelEditorSource(modelData.getId()));

            BpmnModel bpmnModel = new CustomBpmnJsonConverter().convertToBpmnModel(modelNode);
            BpmnXMLConverter xmlConverter = new BpmnXMLConverter();
            if (bpmnModel.getProcesses() == null || bpmnModel.getProcesses().size() <= 0) {
                return "";
            }
            byte[] bpmnBytes = xmlConverter.convertToXML(bpmnModel);
            ByteArrayInputStream in = new ByteArrayInputStream(bpmnBytes);
            InputStreamReader isr = new InputStreamReader(in, StandardCharsets.UTF_8);
            BufferedReader bufferedReader = new BufferedReader(isr);
            StringBuffer xmlContent = new StringBuffer();
            String lineTxt;
            while ((lineTxt = bufferedReader.readLine()) != null) {
                xmlContent.append(lineTxt);
                xmlContent.append("\n");
            }
            isr.close();
            return xmlContent.toString();
        } catch (Exception e) {
            log.error(e.getMessage(), e);
            Asserts.fail("[bpm.model.error.view.fail]");
        }

        return null;
    }

    @Override
    public void deleteModel(String modelId) {
        Model model = repositoryService.createModelQuery().modelId(modelId).singleResult();
        if (model == null) {
            return;
        }

        repositoryService.deleteModel(modelId);
    }

    @Override
    public void updateModel(ActModelDTO modelDTO) {
        String modelId = modelDTO.getId();
        String modelName = modelDTO.getName();
        String key = modelDTO.getKey();
        String category = modelDTO.getCategory();
        String desc = modelDTO.getDesc();

        // 查询模版
        Model model = repositoryService.createModelQuery().modelId(modelId).singleResult();
        String preKey = model.getKey();
        if (model == null) {
            Asserts.fail("[bpm.model.error.nonexistent]");
        }

        // 更新MetaInfo
        ActModelMetaInfo metaInfo = JSONUtil.toBean(model.getMetaInfo(), ActModelMetaInfo.class);
        metaInfo.setDescription(desc);
        model.setMetaInfo(JSONUtil.toJsonStr(metaInfo));

        // 字段更新
        model.setCategory(category);
        model.setKey(key);
        model.setName(modelName);
        repositoryService.saveModel(model);

        // 如果key改变了需要修改xml中的process id值
        if (!StrUtil.equals(preKey, key)) {
            try {
                BpmnJsonConverter jsonConverter = new BpmnJsonConverter();
                ObjectNode modelNode = (ObjectNode) new ObjectMapper().readTree(repositoryService.getModelEditorSource(modelId));
                BpmnModel bpmnModel = jsonConverter.convertToBpmnModel(modelNode);
                // 更新主要流程的id
                Process mainProcess = bpmnModel.getMainProcess();
                if (mainProcess != null) {
                    mainProcess.setId(key);
                    mainProcess.setName(modelName);
                    modelNode = new BpmnJsonConverter().convertToJson(bpmnModel);
                    repositoryService.addModelEditorSource(modelId, modelNode.toString().getBytes(StandardCharsets.UTF_8));
                }
            } catch (Exception e) {
                log.error(e.getMessage(), e);
            }
        }
    }

    @Override
    public void recordModelKey(String modelId) {
        Model model = repositoryService.getModel(modelId);
        // 记录key到MetaInfo
        ActModelMetaInfo metaInfo = JSONUtil.toBean(model.getMetaInfo(), ActModelMetaInfo.class);
        List<String> historyKeys = metaInfo.getHistoryKeys();
        if (!historyKeys.contains(model.getKey())) {
            historyKeys.add(model.getKey());
            model.setMetaInfo(JSONUtil.toJsonStr(metaInfo));
            repositoryService.saveModel(model);
        }
    }
}
